{
 "cells": [
  {
   "cell_type": "code",
   "execution_count": 1,
   "id": "initial_id",
   "metadata": {
    "ExecuteTime": {
     "end_time": "2025-01-21T14:09:39.580119Z",
     "start_time": "2025-01-21T14:09:39.573580Z"
    },
    "execution": {
     "iopub.execute_input": "2025-01-21T15:42:24.052277Z",
     "iopub.status.busy": "2025-01-21T15:42:24.052140Z",
     "iopub.status.idle": "2025-01-21T15:42:25.965327Z",
     "shell.execute_reply": "2025-01-21T15:42:25.964863Z",
     "shell.execute_reply.started": "2025-01-21T15:42:24.052259Z"
    }
   },
   "outputs": [
    {
     "name": "stderr",
     "output_type": "stream",
     "text": [
      "/usr/local/lib/python3.10/site-packages/tqdm/auto.py:21: TqdmWarning: IProgress not found. Please update jupyter and ipywidgets. See https://ipywidgets.readthedocs.io/en/stable/user_install.html\n",
      "  from .autonotebook import tqdm as notebook_tqdm\n"
     ]
    },
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "sys.version_info(major=3, minor=10, micro=14, releaselevel='final', serial=0)\n",
      "matplotlib 3.10.0\n",
      "numpy 1.26.4\n",
      "pandas 2.2.3\n",
      "sklearn 1.6.0\n",
      "torch 2.5.1+cu124\n",
      "cuda:0\n"
     ]
    }
   ],
   "source": [
    "import matplotlib as mpl\n",
    "import matplotlib.pyplot as plt\n",
    "%matplotlib inline\n",
    "import numpy as np\n",
    "import sklearn\n",
    "import pandas as pd\n",
    "import os\n",
    "import sys\n",
    "import time\n",
    "from tqdm.auto import tqdm\n",
    "import torch\n",
    "import torch.nn as nn\n",
    "import torch.nn.functional as F\n",
    "\n",
    "print(sys.version_info)\n",
    "for module in mpl, np, pd, sklearn, torch:\n",
    "    print(module.__name__, module.__version__)\n",
    "\n",
    "device = torch.device(\"cuda:0\") if torch.cuda.is_available() else torch.device(\"cpu\")\n",
    "print(device)\n",
    "\n",
    "seed = 42"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 2,
   "id": "1dad802b6500ab83",
   "metadata": {
    "ExecuteTime": {
     "end_time": "2025-01-21T14:09:41.656263Z",
     "start_time": "2025-01-21T14:09:39.699901Z"
    },
    "ExecutionIndicator": {
     "show": true
    },
    "execution": {
     "iopub.execute_input": "2025-01-21T15:42:25.966713Z",
     "iopub.status.busy": "2025-01-21T15:42:25.966306Z",
     "iopub.status.idle": "2025-01-21T15:42:27.746664Z",
     "shell.execute_reply": "2025-01-21T15:42:27.746064Z",
     "shell.execute_reply.started": "2025-01-21T15:42:25.966696Z"
    },
    "tags": []
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "[(PosixPath('competitions/cifar-10/train/1.png'), 'frog'),\n",
      " (PosixPath('competitions/cifar-10/train/2.png'), 'truck'),\n",
      " (PosixPath('competitions/cifar-10/train/3.png'), 'truck'),\n",
      " (PosixPath('competitions/cifar-10/train/4.png'), 'deer'),\n",
      " (PosixPath('competitions/cifar-10/train/5.png'), 'automobile')]\n",
      "[(PosixPath('competitions/cifar-10/test/1.png'), 'cat'),\n",
      " (PosixPath('competitions/cifar-10/test/2.png'), 'cat'),\n",
      " (PosixPath('competitions/cifar-10/test/3.png'), 'cat'),\n",
      " (PosixPath('competitions/cifar-10/test/4.png'), 'cat'),\n",
      " (PosixPath('competitions/cifar-10/test/5.png'), 'cat')]\n",
      "50000 300000\n"
     ]
    }
   ],
   "source": [
    "from pathlib import Path\n",
    "\n",
    "# 目的：通过读取csv文件，得到图片的类别，并将图片路径和类别保存到DataFrame中。\n",
    "\n",
    "DATA_DIR1 = Path(\"./\")\n",
    "DATA_DIR2=Path(\"./competitions/cifar-10/\")\n",
    "\n",
    "train_labels_file = DATA_DIR1 / \"trainLabels.csv\"\n",
    "test_csv_file = DATA_DIR1 / \"sampleSubmission.csv\"  # 测试集模板csv文件\n",
    "train_folder = DATA_DIR2 / \"train/\"\n",
    "test_folder = DATA_DIR2 / \"test/\"\n",
    "\n",
    "# 所有的类别\n",
    "class_names = ['airplane', 'automobile', 'bird', 'cat', 'deer', 'dog', 'frog', 'horse', 'ship', 'truck']\n",
    "\n",
    "\n",
    "# filepath:csv文件路径，folder:图片所在文件夹\n",
    "def parse_csv_file(filepath, folder):\n",
    "    result = []\n",
    "    # 读取所有行\n",
    "    # with 语句：用于管理文件的上下文。在 with 块结束时，文件会自动关闭，无需手动调用 f.close()。\n",
    "    with open(filepath, 'r') as f:\n",
    "        # f.readlines()：读取文件的所有行，返回一个列表，每个元素是一行内容。\n",
    "        # 第一行不需要，因为第一行是标题\n",
    "        lines = f.readlines()[1:]\n",
    "    for line in lines:\n",
    "        # strip('\\n')：去除行末的换行符（\\n）。\n",
    "        # split(',')：按','分割字符串，返回一个列表。\n",
    "        image_id, label_str = line.strip('\\n').split(',')\n",
    "        # 得到图片的路径\n",
    "        image_full_path = folder / f\"{image_id}.png\"\n",
    "        # 得到对应图片的路径和分类\n",
    "        result.append((image_full_path, label_str))\n",
    "    return result\n",
    "\n",
    "\n",
    "# 得到训练集和测试集的图片路径和分类\n",
    "train_labels_info = parse_csv_file(train_labels_file, train_folder)\n",
    "test_csv_info = parse_csv_file(test_csv_file, test_folder)\n",
    "\n",
    "#打印\n",
    "import pprint\n",
    "\n",
    "pprint.pprint(train_labels_info[0:5])\n",
    "pprint.pprint(test_csv_info[0:5])\n",
    "print(len(train_labels_info), len(test_csv_info))"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 3,
   "id": "8cd5063c39c678cf",
   "metadata": {
    "ExecuteTime": {
     "end_time": "2025-01-21T14:09:41.732204Z",
     "start_time": "2025-01-21T14:09:41.658275Z"
    },
    "execution": {
     "iopub.execute_input": "2025-01-21T15:42:27.747375Z",
     "iopub.status.busy": "2025-01-21T15:42:27.747226Z",
     "iopub.status.idle": "2025-01-21T15:42:27.799621Z",
     "shell.execute_reply": "2025-01-21T15:42:27.799067Z",
     "shell.execute_reply.started": "2025-01-21T15:42:27.747360Z"
    }
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "                            filepath       class\n",
      "0  competitions/cifar-10/train/1.png        frog\n",
      "1  competitions/cifar-10/train/2.png       truck\n",
      "2  competitions/cifar-10/train/3.png       truck\n",
      "3  competitions/cifar-10/train/4.png        deer\n",
      "4  competitions/cifar-10/train/5.png  automobile\n",
      "                                filepath       class\n",
      "0  competitions/cifar-10/train/45001.png       horse\n",
      "1  competitions/cifar-10/train/45002.png  automobile\n",
      "2  competitions/cifar-10/train/45003.png        deer\n",
      "3  competitions/cifar-10/train/45004.png  automobile\n",
      "4  competitions/cifar-10/train/45005.png    airplane\n",
      "                           filepath class\n",
      "0  competitions/cifar-10/test/1.png   cat\n",
      "1  competitions/cifar-10/test/2.png   cat\n",
      "2  competitions/cifar-10/test/3.png   cat\n",
      "3  competitions/cifar-10/test/4.png   cat\n",
      "4  competitions/cifar-10/test/5.png   cat\n"
     ]
    }
   ],
   "source": [
    "# 取前45000张图片作为训练集\n",
    "train_df = pd.DataFrame(train_labels_info[0:45000])\n",
    "# 取后5000张图片作为验证集\n",
    "valid_df = pd.DataFrame(train_labels_info[45000:])\n",
    "# 测试集\n",
    "test_df = pd.DataFrame(test_csv_info)\n",
    "\n",
    "# 为 Pandas DataFrame 的列重新命名\n",
    "train_df.columns = ['filepath', 'class']\n",
    "valid_df.columns = ['filepath', 'class']\n",
    "test_df.columns = ['filepath', 'class']\n",
    "\n",
    "print(train_df.head())\n",
    "print(valid_df.head())\n",
    "print(test_df.head())"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 4,
   "id": "af9444393c2debca",
   "metadata": {
    "ExecuteTime": {
     "end_time": "2025-01-21T14:09:41.740688Z",
     "start_time": "2025-01-21T14:09:41.732204Z"
    },
    "execution": {
     "iopub.execute_input": "2025-01-21T15:42:27.800389Z",
     "iopub.status.busy": "2025-01-21T15:42:27.800187Z",
     "iopub.status.idle": "2025-01-21T15:42:28.478973Z",
     "shell.execute_reply": "2025-01-21T15:42:28.478513Z",
     "shell.execute_reply.started": "2025-01-21T15:42:27.800373Z"
    }
   },
   "outputs": [],
   "source": [
    "from PIL import Image\n",
    "from torch.utils.data import Dataset, DataLoader\n",
    "from torchvision import transforms\n",
    "\n",
    "\n",
    "class Cifar10Dataset(Dataset):\n",
    "    df_map = {\n",
    "        \"train\": train_df,\n",
    "        \"eval\": valid_df,\n",
    "        \"test\": test_df\n",
    "    }\n",
    "    # enumerate(class_names)：遍历 class_names，返回索引和类别名称的元组\n",
    "    # 将类别名称映射为索引，通常用于将标签转换为模型可以处理的数值。\n",
    "    label_to_idx = {\n",
    "        label: idx for idx, label in enumerate(class_names)\n",
    "    }\n",
    "    # 将索引映射为类别名称，通常用于将模型的输出（索引）转换回可读的类别名称\n",
    "    idx_to_label = {\n",
    "        idx: label for idx, label in enumerate(class_names)\n",
    "    }\n",
    "\n",
    "    def __init__(self, mode, transform=None):\n",
    "        # 获取对应模式的df，不同字符串对应不同模式\n",
    "        self.df = self.df_map.get(mode, None)\n",
    "        if self.df is None:\n",
    "            raise ValueError(f\"Invalid mode: {mode}\")\n",
    "        self.transform = transform\n",
    "\n",
    "    def __getitem__(self, index):\n",
    "        # 获取图片路径和标签\n",
    "        img_path, label = self.df.iloc[index]\n",
    "        # 打开一张图片并将其转换为 RGB 格式\n",
    "        img = Image.open(img_path).convert('RGB')  # 确保图片具有三个通道\n",
    "        # 应用数据增强\n",
    "        img = self.transform(img)\n",
    "        # label 转换为 idx\n",
    "        label = self.label_to_idx[label]\n",
    "        return img, label\n",
    "\n",
    "    def __len__(self):\n",
    "        # 返回df的行数,样本数\n",
    "        return self.df.shape[0]\n",
    "\n",
    "\n",
    "IMAGE_SIZE = 32\n",
    "mean, std = [0.4914, 0.4822, 0.4465], [0.247, 0.243, 0.261]\n",
    "\n",
    "#数据增强\n",
    "# ToTensor还将图像的维度从[height, width, channels]转换为[channels, height, width]。\n",
    "transforms_train = transforms.Compose([\n",
    "    transforms.Resize((IMAGE_SIZE, IMAGE_SIZE)),  # 缩放\n",
    "    transforms.RandomRotation(40),  # 随机旋转\n",
    "    transforms.RandomHorizontalFlip(),  # 随机水平翻转\n",
    "    transforms.ToTensor(),  # 转换为Tensor\n",
    "    transforms.Normalize(mean, std)  # 标准化\n",
    "])\n",
    "\n",
    "transforms_eval = transforms.Compose([\n",
    "    transforms.Resize((IMAGE_SIZE, IMAGE_SIZE)),\n",
    "    transforms.ToTensor(),\n",
    "    transforms.Normalize(mean, std)\n",
    "])\n",
    "\n",
    "train_ds = Cifar10Dataset(mode=\"train\", transform=transforms_train)\n",
    "eval_ds = Cifar10Dataset(mode=\"eval\", transform=transforms_eval)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 5,
   "id": "d17ef4a56f0b6ae1",
   "metadata": {
    "ExecuteTime": {
     "end_time": "2025-01-21T14:09:41.748174Z",
     "start_time": "2025-01-21T14:09:41.742701Z"
    },
    "execution": {
     "iopub.execute_input": "2025-01-21T15:42:28.479796Z",
     "iopub.status.busy": "2025-01-21T15:42:28.479497Z",
     "iopub.status.idle": "2025-01-21T15:42:28.487379Z",
     "shell.execute_reply": "2025-01-21T15:42:28.487015Z",
     "shell.execute_reply.started": "2025-01-21T15:42:28.479779Z"
    }
   },
   "outputs": [
    {
     "data": {
      "text/plain": [
       "torch.Size([3, 32, 32])"
      ]
     },
     "execution_count": 5,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "train_ds[0][0].shape"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 6,
   "id": "161282a4e612afcd",
   "metadata": {
    "ExecuteTime": {
     "end_time": "2025-01-21T14:09:41.753134Z",
     "start_time": "2025-01-21T14:09:41.749187Z"
    },
    "execution": {
     "iopub.execute_input": "2025-01-21T15:42:28.488913Z",
     "iopub.status.busy": "2025-01-21T15:42:28.488648Z",
     "iopub.status.idle": "2025-01-21T15:42:28.491471Z",
     "shell.execute_reply": "2025-01-21T15:42:28.491088Z",
     "shell.execute_reply.started": "2025-01-21T15:42:28.488898Z"
    }
   },
   "outputs": [],
   "source": [
    "batch_size = 64\n",
    "\n",
    "train_loader = DataLoader(train_ds, batch_size=batch_size,\n",
    "                          shuffle=True)\n",
    "eval_loader = DataLoader(eval_ds, batch_size=batch_size,\n",
    "                         shuffle=False)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "20b5b31416450980",
   "metadata": {},
   "source": [
    "## 模型定义"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 7,
   "id": "b73daeeb83b4b527",
   "metadata": {
    "ExecuteTime": {
     "end_time": "2025-01-21T14:09:41.760182Z",
     "start_time": "2025-01-21T14:09:41.754138Z"
    },
    "execution": {
     "iopub.execute_input": "2025-01-21T15:42:28.492055Z",
     "iopub.status.busy": "2025-01-21T15:42:28.491899Z",
     "iopub.status.idle": "2025-01-21T15:42:28.497100Z",
     "shell.execute_reply": "2025-01-21T15:42:28.496702Z",
     "shell.execute_reply.started": "2025-01-21T15:42:28.492041Z"
    }
   },
   "outputs": [],
   "source": [
    "class Resdiual(nn.Module):\n",
    "    \"\"\"\n",
    "    浅层的残差块，无bottleneck（性能限制\n",
    "    \"\"\"\n",
    "\n",
    "    def __init__(self, input_channels, output_channels,\n",
    "                 use_1x1conv=False, stride=1):\n",
    "        \"\"\"\n",
    "        残差块\n",
    "        params filters: 过滤器数目，决定输出通道\n",
    "        params use_1x1conv: 是否使用 1x1 卷积，此时 stride=2，进行降采样\n",
    "        params strides: 步长，默认为1，当降采样的时候设置为2\n",
    "        \"\"\"\n",
    "        super().__init__()\n",
    "        # [1+(n+2-3)]//1=n \n",
    "        # [1+(n+2-3)]//2=n//2\n",
    "        # 即该方法可能会降采样\n",
    "        self.conv1 = nn.Conv2d(\n",
    "            in_channels=input_channels,\n",
    "            out_channels=output_channels,\n",
    "            kernel_size=3,\n",
    "            stride=stride,\n",
    "            padding=1\n",
    "        )\n",
    "        # [1+(n+2-3)]//1=n \n",
    "        self.conv2 = nn.Conv2d(\n",
    "            in_channels=output_channels,\n",
    "            out_channels=output_channels,\n",
    "            kernel_size=3,\n",
    "            stride=1,\n",
    "            padding=1\n",
    "        )\n",
    "        if use_1x1conv:\n",
    "            # skip connection 的 1x1 卷积，用于改变通道数和降采样，使得最终可以做残差连接\n",
    "            # [1+n-1]//s=n//s\n",
    "            self.conv_sc = nn.Conv2d(\n",
    "                in_channels=input_channels,\n",
    "                out_channels=output_channels,\n",
    "                kernel_size=1,\n",
    "                stride=stride\n",
    "            )\n",
    "        else:\n",
    "            self.conv_sc = None\n",
    "\n",
    "        self.bn1 = nn.BatchNorm2d(output_channels, eps=1e-5, momentum=0.9)\n",
    "        self.bn2 = nn.BatchNorm2d(output_channels, eps=1e-5, momentum=0.9)\n",
    "\n",
    "    def forward(self, inputs):\n",
    "        flow = F.relu(self.bn1(self.conv1(inputs)))  # 卷积->BN->ReLU\n",
    "        flow = self.bn2(self.conv2(flow))  # 卷积->BN\n",
    "        if self.conv_sc:\n",
    "            inputs = self.conv_sc(inputs)  # 1x1卷积，改变通道数和降采样\n",
    "        # 残差连接->ReLU，必须保证flow和inputs的shape相同\n",
    "        return F.relu(flow + inputs)\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 8,
   "id": "fc16467b5391fd55",
   "metadata": {
    "ExecuteTime": {
     "end_time": "2025-01-21T14:09:41.766476Z",
     "start_time": "2025-01-21T14:09:41.761185Z"
    },
    "execution": {
     "iopub.execute_input": "2025-01-21T15:42:28.497798Z",
     "iopub.status.busy": "2025-01-21T15:42:28.497647Z",
     "iopub.status.idle": "2025-01-21T15:42:28.501599Z",
     "shell.execute_reply": "2025-01-21T15:42:28.501222Z",
     "shell.execute_reply.started": "2025-01-21T15:42:28.497784Z"
    }
   },
   "outputs": [],
   "source": [
    "class ResdiualBlock(nn.Module):\n",
    "    \"\"\"\n",
    "    若干个 Resdiual 模块堆叠在一起，\n",
    "    通常在第一个模块给 skip connection 使用 1x1conv with stride=2\n",
    "    \"\"\"\n",
    "\n",
    "    def __init__(self, input_channels, output_channels, num, is_first=False):\n",
    "        \"\"\"\n",
    "        params filters: 过滤器数目\n",
    "        params num: 堆叠几个 Resdiual 模块\n",
    "        params is_first: 是不是第一个block。 最上面一层 Resdiual 的 stride=1,is_first=False,图像尺寸减半，False图像尺寸不变\n",
    "        \"\"\"\n",
    "        super().__init__()\n",
    "        self.model = nn.Sequential()  # 用于存放 Resdiual 模块\n",
    "        # append() 等价于 add_module()\n",
    "        self.model.append(Resdiual(\n",
    "            input_channels=input_channels,\n",
    "            output_channels=output_channels,\n",
    "            use_1x1conv=not is_first,\n",
    "            stride=1 if is_first else 2\n",
    "        ))  # 第一个 Resdiual 模块，负责通道翻倍,图像的尺寸减半\n",
    "        for _ in range(1, num):\n",
    "            # 堆叠 num 个 Resdiual 模块\n",
    "            self.model.append(Resdiual(\n",
    "                input_channels=output_channels,\n",
    "                output_channels=output_channels,\n",
    "                use_1x1conv=False,\n",
    "                stride=1\n",
    "            ))\n",
    "\n",
    "    def forward(self, inputs):\n",
    "        return self.model(inputs)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 9,
   "id": "8128761d5d6c0e1d",
   "metadata": {
    "ExecuteTime": {
     "end_time": "2025-01-21T14:09:41.773688Z",
     "start_time": "2025-01-21T14:09:41.767479Z"
    },
    "execution": {
     "iopub.execute_input": "2025-01-21T15:42:28.502300Z",
     "iopub.status.busy": "2025-01-21T15:42:28.502064Z",
     "iopub.status.idle": "2025-01-21T15:42:28.507166Z",
     "shell.execute_reply": "2025-01-21T15:42:28.506783Z",
     "shell.execute_reply.started": "2025-01-21T15:42:28.502285Z"
    }
   },
   "outputs": [],
   "source": [
    "class ResNetCifar10(nn.Module):\n",
    "    def __init__(self, n=3, num_classes=10):\n",
    "        \"\"\"\n",
    "        params units: 预测类别的数目\n",
    "        \"\"\"\n",
    "        super().__init__()\n",
    "        self.model = nn.Sequential(\n",
    "            # conv1\n",
    "            nn.Conv2d(in_channels=3, out_channels=16,\n",
    "                      kernel_size=3, stride=1),\n",
    "            nn.BatchNorm2d(16, momentum=0.9, eps=1e-5),\n",
    "            nn.ReLU(),\n",
    "            # conv2_x\n",
    "            ResdiualBlock(input_channels=16, output_channels=16,\n",
    "                          num=2 * n, is_first=True),\n",
    "            # conv3_x\n",
    "            ResdiualBlock(input_channels=16, output_channels=32,\n",
    "                          num=2 * n),\n",
    "            # conv4_x\n",
    "            ResdiualBlock(input_channels=32, output_channels=64,\n",
    "                          num=2 * n),\n",
    "            # 全局平均池化层\n",
    "            #无论输入图片大小，输出都是1x1，把width和height压缩为1\n",
    "            nn.AdaptiveAvgPool2d((1, 1)),\n",
    "            # 全连接层\n",
    "            nn.Flatten(),  # 64*1*1 -> 64\n",
    "            # 输出层\n",
    "            nn.Linear(in_features=64, out_features=num_classes)\n",
    "        )\n",
    "        self.init_weights()\n",
    "\n",
    "    def init_weights(self):\n",
    "        for m in self.modules():\n",
    "            if isinstance(m, (nn.Linear, nn.Conv2d)):\n",
    "                # Kaiming 初始化（也称为 He 初始化）是为深度神经网络设计的一种初始化方法，特别适用于 ReLU 激活函数。\n",
    "                nn.init.kaiming_uniform_(m.weight)\n",
    "                if m.bias is not None:\n",
    "                    nn.init.zeros_(m.bias)\n",
    "    \n",
    "    def forward(self, inputs):\n",
    "        return self.model(inputs)\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 10,
   "id": "bb519b4756d62090",
   "metadata": {
    "ExecuteTime": {
     "end_time": "2025-01-21T14:09:41.777720Z",
     "start_time": "2025-01-21T14:09:41.774691Z"
    },
    "execution": {
     "iopub.execute_input": "2025-01-21T15:42:28.507916Z",
     "iopub.status.busy": "2025-01-21T15:42:28.507731Z",
     "iopub.status.idle": "2025-01-21T15:42:28.509918Z",
     "shell.execute_reply": "2025-01-21T15:42:28.509449Z",
     "shell.execute_reply.started": "2025-01-21T15:42:28.507900Z"
    }
   },
   "outputs": [],
   "source": [
    "# for key, value in ResNetCifar10(len(class_names)).named_parameters():\n",
    "#     print(f\"{key:^40}paramerters num: {np.prod(value.shape)}\")"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 11,
   "id": "b294d25e7f6287d0",
   "metadata": {
    "ExecuteTime": {
     "end_time": "2025-01-21T14:09:41.825513Z",
     "start_time": "2025-01-21T14:09:41.779726Z"
    },
    "execution": {
     "iopub.execute_input": "2025-01-21T15:42:28.510536Z",
     "iopub.status.busy": "2025-01-21T15:42:28.510356Z",
     "iopub.status.idle": "2025-01-21T15:42:28.560029Z",
     "shell.execute_reply": "2025-01-21T15:42:28.559570Z",
     "shell.execute_reply.started": "2025-01-21T15:42:28.510522Z"
    }
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Total trainable parameters: 1929546\n"
     ]
    }
   ],
   "source": [
    "total_params = sum(p.numel() for p in ResNetCifar10(len(class_names)).parameters() if p.requires_grad)\n",
    "print(f\"Total trainable parameters: {total_params}\")"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "76ca49d0dca74376",
   "metadata": {},
   "source": [
    "## 模型训练"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 12,
   "id": "632d31ce33563b0c",
   "metadata": {
    "ExecuteTime": {
     "end_time": "2025-01-21T14:09:41.909287Z",
     "start_time": "2025-01-21T14:09:41.826518Z"
    },
    "execution": {
     "iopub.execute_input": "2025-01-21T15:42:28.560621Z",
     "iopub.status.busy": "2025-01-21T15:42:28.560480Z",
     "iopub.status.idle": "2025-01-21T15:42:28.584869Z",
     "shell.execute_reply": "2025-01-21T15:42:28.584475Z",
     "shell.execute_reply.started": "2025-01-21T15:42:28.560607Z"
    }
   },
   "outputs": [],
   "source": [
    "from sklearn.metrics import accuracy_score\n",
    "\n",
    "\n",
    "@torch.no_grad()  # 装饰器，禁止梯度计算\n",
    "def evaluate(model, data_loader, loss_fct):\n",
    "    loss_list = []\n",
    "    pred_list = []\n",
    "    label_list = []\n",
    "    for datas, labels in data_loader:\n",
    "        datas = datas.to(device)\n",
    "        labels = labels.to(device)\n",
    "\n",
    "        # 前向传播\n",
    "        logits = model(datas)\n",
    "        loss = loss_fct(logits, labels)  # 验证集损失\n",
    "        # tensor.item() 获取tensor的数值，loss是只有一个元素的tensor\n",
    "        loss_list.append(loss.item())\n",
    "\n",
    "        # 预测\n",
    "        preds = logits.argmax(axis=-1)  # 预测类别\n",
    "        pred_list.extend(preds.cpu().numpy().tolist())  # tensor转numpy，再转list\n",
    "        label_list.extend(labels.cpu().numpy().tolist())\n",
    "\n",
    "    acc = accuracy_score(label_list, pred_list)  # 计算准确率\n",
    "    return np.mean(loss_list), acc  # # 返回验证集平均损失和准确率"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 13,
   "id": "28c2ba49f4ff546",
   "metadata": {
    "ExecuteTime": {
     "end_time": "2025-01-21T14:09:41.915505Z",
     "start_time": "2025-01-21T14:09:41.910290Z"
    },
    "execution": {
     "iopub.execute_input": "2025-01-21T15:42:28.585985Z",
     "iopub.status.busy": "2025-01-21T15:42:28.585436Z",
     "iopub.status.idle": "2025-01-21T15:42:28.590840Z",
     "shell.execute_reply": "2025-01-21T15:42:28.590327Z",
     "shell.execute_reply.started": "2025-01-21T15:42:28.585956Z"
    }
   },
   "outputs": [],
   "source": [
    "class SaveCheckpointsCallback:\n",
    "    def __init__(self, save_dir, save_step=500, save_best_only=True):\n",
    "        self.save_dir = save_dir  # 保存路径\n",
    "        self.save_step = save_step  # 保存步数\n",
    "        self.save_best_only = save_best_only  # 是否只保存最好的模型\n",
    "        self.best_metric = -1  # 最好的指标，指标不可能为负数，所以初始化为-1\n",
    "        # 创建保存路径\n",
    "        if not os.path.exists(self.save_dir):  # 如果不存在保存路径，则创建\n",
    "            os.makedirs(self.save_dir)\n",
    "\n",
    "    # 对象被调用时：当你将对象像函数一样调用时，Python 会自动调用 __call__ 方法。\n",
    "    # state_dict() 返回模型参数的字典，包括模型参数和优化器参数\n",
    "    # metric 是指标，可以是验证集的准确率，也可以是其他指标\n",
    "    def __call__(self, step, state_dict, metric=None):\n",
    "        if step % self.save_step > 0:\n",
    "            return  # 不是保存步数，则直接返回\n",
    "\n",
    "        if self.save_best_only:\n",
    "            assert metric is not None  # 必须传入metric\n",
    "            if metric >= self.best_metric:  # 如果当前指标大于最好的指标\n",
    "                # save checkpoint\n",
    "                # 保存最好的模型，覆盖之前的模型，不保存step，只保存state_dict，即模型参数，不保存优化器参数\n",
    "                torch.save(state_dict, os.path.join(self.save_dir, \"08_resnet.ckpt\"))\n",
    "                self.best_metric = metric  # 更新最好的指标\n",
    "        else:\n",
    "            # 保存模型\n",
    "            torch.save(state_dict, os.path.join(self.save_dir, f\"{step}.ckpt\"))\n",
    "            # 保存每个step的模型，不覆盖之前的模型，保存step，保存state_dict，即模型参数，不保存优化器参数\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 14,
   "id": "cd4d61586ad24eb5",
   "metadata": {
    "ExecuteTime": {
     "end_time": "2025-01-21T14:09:41.921575Z",
     "start_time": "2025-01-21T14:09:41.916508Z"
    },
    "execution": {
     "iopub.execute_input": "2025-01-21T15:42:28.591954Z",
     "iopub.status.busy": "2025-01-21T15:42:28.591558Z",
     "iopub.status.idle": "2025-01-21T15:42:28.596343Z",
     "shell.execute_reply": "2025-01-21T15:42:28.595924Z",
     "shell.execute_reply.started": "2025-01-21T15:42:28.591926Z"
    }
   },
   "outputs": [],
   "source": [
    "class EarlyStopCallback:\n",
    "    def __init__(self, patience=5, min_delta=0.01):\n",
    "        self.patience = patience  # 多少个step没有提升就停止训练\n",
    "        self.min_delta = min_delta  # 最小的提升幅度\n",
    "        self.best_metric = -1  # 记录的最好的指标\n",
    "        self.counter = 0  # 计数器，记录连续多少个step没有提升\n",
    "\n",
    "    def __call__(self, metric):\n",
    "        if metric >= self.best_metric + self.min_delta:  # 如果指标提升了\n",
    "            self.best_metric = metric  # 更新最好的指标\n",
    "            self.counter = 0  # 计数器清零\n",
    "        else:\n",
    "            self.counter += 1  # 计数器加一\n",
    "\n",
    "    @property  # 使用@property装饰器，使得 对象.early_stop可以调用，不需要()\n",
    "    def early_stop(self):\n",
    "        # 如果计数器大于等于patience，则返回True，停止训练\n",
    "        return self.counter >= self.patience"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 15,
   "id": "725cf8a521379801",
   "metadata": {
    "ExecuteTime": {
     "end_time": "2025-01-21T14:09:41.930997Z",
     "start_time": "2025-01-21T14:09:41.922578Z"
    },
    "execution": {
     "iopub.execute_input": "2025-01-21T15:42:28.597327Z",
     "iopub.status.busy": "2025-01-21T15:42:28.597094Z",
     "iopub.status.idle": "2025-01-21T15:42:28.605257Z",
     "shell.execute_reply": "2025-01-21T15:42:28.604836Z",
     "shell.execute_reply.started": "2025-01-21T15:42:28.597302Z"
    }
   },
   "outputs": [],
   "source": [
    "def training(model,\n",
    "             train_loader,\n",
    "             val_loader,\n",
    "             epoch,\n",
    "             loss_fct,\n",
    "             optimizer,\n",
    "             save_ckpt_callback=None,\n",
    "             early_stop_callback=None,\n",
    "             eval_step=500,\n",
    "             ):\n",
    "    record_dict = {\n",
    "        \"train\": [],\n",
    "        \"val\": []\n",
    "    }\n",
    "\n",
    "    global_step = 0  # 全局步数\n",
    "    model.train()  # 训练模式\n",
    "    with tqdm(total=epoch * len(train_loader)) as pbar:\n",
    "        for epoch_id in range(epoch):\n",
    "            for datas, labels in train_loader:\n",
    "                datas = datas.to(device)\n",
    "                labels = labels.to(device)\n",
    "\n",
    "                # 前向传播\n",
    "                logits = model(datas)\n",
    "                loss = loss_fct(logits, labels)  # 训练集损失\n",
    "                preds = logits.argmax(axis=-1)  # 预测类别\n",
    "\n",
    "                # 反向传播\n",
    "                optimizer.zero_grad()  # 梯度清零\n",
    "                loss.backward()  # 反向传播\n",
    "                optimizer.step()  # 优化器更新参数\n",
    "\n",
    "                # 计算准确率\n",
    "                acc = accuracy_score(labels.cpu().numpy(), preds.cpu().numpy())\n",
    "                loss = loss.cpu().item()\n",
    "\n",
    "                record_dict[\"train\"].append({\n",
    "                    \"loss\": loss,\n",
    "                    \"acc\": acc,\n",
    "                    \"step\": global_step\n",
    "                })\n",
    "\n",
    "                # 评估\n",
    "                if global_step % eval_step == 0:\n",
    "                    model.eval()  # 评估模式\n",
    "                    # 验证集损失和准确率\n",
    "                    val_loss, val_acc = evaluate(model, val_loader, loss_fct)\n",
    "                    record_dict[\"val\"].append({\n",
    "                        \"loss\": val_loss,\n",
    "                        \"acc\": val_acc,\n",
    "                        \"step\": global_step\n",
    "                    })\n",
    "                    model.train()  # 训练模式\n",
    "\n",
    "                    # 2. 保存模型权重 save model checkpoint\n",
    "                    if save_ckpt_callback is not None:\n",
    "                        # model.state_dict() 返回模型参数的字典，包括模型参数和优化器参数\n",
    "                        save_ckpt_callback(global_step, model.state_dict(), val_acc)\n",
    "                        # 保存最好的模型，覆盖之前的模型，保存step，保存state_dict,通过metric判断是否保存最好的模型\n",
    "\n",
    "                    # 3. 早停 early stopping\n",
    "                    if early_stop_callback is not None:\n",
    "                        # 验证集准确率不再提升，则停止训练\n",
    "                        early_stop_callback(val_acc)\n",
    "                        # 验证集准确率不再提升，则停止训练\n",
    "                        if early_stop_callback.early_stop:\n",
    "                            print(f\"Early stop at epoch {epoch_id} / global_step {global_step}\")\n",
    "                            return record_dict  # 早停，返回记录字典 record_dict\n",
    "\n",
    "                # 更新进度条和全局步数\n",
    "                pbar.update(1)  # 更新进度条\n",
    "                global_step += 1  # 全局步数加一\n",
    "                pbar.set_postfix({\"epoch\": epoch_id})\n",
    "\n",
    "    return record_dict  # 训练结束，返回记录字典 record_dict\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 16,
   "id": "22bf1e82c9715088",
   "metadata": {
    "ExecuteTime": {
     "end_time": "2025-01-21T14:09:41.979444Z",
     "start_time": "2025-01-21T14:09:41.931999Z"
    },
    "ExecutionIndicator": {
     "show": true
    },
    "execution": {
     "iopub.execute_input": "2025-01-21T15:42:28.606046Z",
     "iopub.status.busy": "2025-01-21T15:42:28.605800Z",
     "iopub.status.idle": "2025-01-21T15:42:28.653156Z",
     "shell.execute_reply": "2025-01-21T15:42:28.652778Z",
     "shell.execute_reply.started": "2025-01-21T15:42:28.606020Z"
    },
    "tags": []
   },
   "outputs": [],
   "source": [
    "epoch = 100\n",
    "\n",
    "model = ResNetCifar10(len(class_names))  # 定义模型\n",
    "\n",
    "# 1. 定义损失函数 采用MSE损失\n",
    "loss_fct = nn.CrossEntropyLoss()\n",
    "\n",
    "# 2. 定义优化器 采用 adam\n",
    "optimizer = torch.optim.Adam(model.parameters(), lr=0.001)\n",
    "\n",
    "# 3.save model checkpoint\n",
    "if not os.path.exists(\"checkpoints\"):\n",
    "    os.makedirs(\"checkpoints\")\n",
    "save_ckpt_callback = SaveCheckpointsCallback(save_dir=\"checkpoints\", save_step=len(train_loader), save_best_only=True)\n",
    "\n",
    "# 4. early stopping\n",
    "early_stop_callback = EarlyStopCallback(patience=5, min_delta=0.01)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 17,
   "id": "5335f62fe5e4977f",
   "metadata": {
    "ExecuteTime": {
     "end_time": "2025-01-21T14:10:19.118493Z",
     "start_time": "2025-01-21T14:09:41.980446Z"
    },
    "execution": {
     "iopub.execute_input": "2025-01-21T15:42:28.654067Z",
     "iopub.status.busy": "2025-01-21T15:42:28.653822Z",
     "iopub.status.idle": "2025-01-21T16:07:43.088942Z",
     "shell.execute_reply": "2025-01-21T16:07:43.088510Z",
     "shell.execute_reply.started": "2025-01-21T15:42:28.654042Z"
    }
   },
   "outputs": [
    {
     "name": "stderr",
     "output_type": "stream",
     "text": [
      " 30%|███       | 21120/70400 [25:14<58:53, 13.95it/s, epoch=29]  "
     ]
    },
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Early stop at epoch 30 / global_step 21120\n"
     ]
    },
    {
     "name": "stderr",
     "output_type": "stream",
     "text": [
      "\n"
     ]
    }
   ],
   "source": [
    "model = model.to(device)  # 将模型移到GPU上\n",
    "\n",
    "# 训练过程\n",
    "record_dict = training(\n",
    "    model,\n",
    "    train_loader,\n",
    "    eval_loader,\n",
    "    epoch,\n",
    "    loss_fct,\n",
    "    optimizer,\n",
    "    save_ckpt_callback=save_ckpt_callback,\n",
    "    early_stop_callback=early_stop_callback,\n",
    "    eval_step=len(train_loader)\n",
    ")"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 18,
   "id": "1a790e5f4635d5e8",
   "metadata": {
    "ExecuteTime": {
     "end_time": "2025-01-21T14:10:19.120498Z",
     "start_time": "2025-01-21T14:10:19.119497Z"
    },
    "ExecutionIndicator": {
     "show": true
    },
    "execution": {
     "iopub.execute_input": "2025-01-21T16:07:43.090965Z",
     "iopub.status.busy": "2025-01-21T16:07:43.090667Z",
     "iopub.status.idle": "2025-01-21T16:07:43.246693Z",
     "shell.execute_reply": "2025-01-21T16:07:43.246268Z",
     "shell.execute_reply.started": "2025-01-21T16:07:43.090948Z"
    },
    "tags": []
   },
   "outputs": [
    {
     "data": {
      "image/png": "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DzyAzaYOXhEROTnlJpGT054qcgaNGjWKSZMmMWLECGbPns1vf/tbampqTrr8li1b2L17N8nJySQlJZGUlER6ejotLS3s2bOn3XYTEhKij4uLi2lsbGT//v1d+n5EROTcp9wkcnLqGRI5g+x2O0uWLGH16tW89dZbPP744/z4xz9m3bp1HS7f2NhIUVERzz///AnPZWVldXVzRUQkDig3iZyciiGRM8yyLCZOnMjEiRO555576Nu3L6+++ioul4tQKNRu2dGjR7No0SKys7NJSUk56Ta3bNlCc3MzXq8XgLVr15KUlERhYWGXvhcRETk/KDeJdEyHyYmcQevWreMXv/gFGzZsoKysjL/85S8cPnyYoUOH0q9fP7Zu3cquXbs4cuQIgUCA6667jszMTGbMmMHKlSspLS2lpKSEf/u3f6O8vDy6Xb/fz0033cT27dt54403WLhwIfPnz9cx2SIickrKTSInp54hkTMoJSWFFStW8Oijj1JfX0/fvn15+OGHmT59OmPGjKGkpIQxY8bQ2NjIsmXLuOyyy1ixYgU//OEPmTVrFg0NDRQUFDBp0qR2/42bNGkSAwcO5JJLLqG1tZW5c+dy7733dt8bFRGRc4Zyk8jJWcYY092NEJGTu/HGG6mtrWXx4sXd3RQRERFAuUnOH+rHFBERERGRuKRiSERERERE4pIOkxMRERERkbikniEREREREYlLKoZERERERCQuqRgSEREREZG4pGJIRERERETikoohERERERGJSyqGREREREQkLqkYEhERERGRuKRiSERERERE4pKKIRERERERiUv/H0SRktz1fI4TAAAAAElFTkSuQmCC",
      "text/plain": [
       "<Figure size 1000x500 with 2 Axes>"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    }
   ],
   "source": [
    "def plot_record_curves(record_dict, sample_step=500):\n",
    "    # .set_index(\"step\") 将 step 列设置为 DataFrame 的索引\n",
    "    train_df = pd.DataFrame(record_dict[\"train\"]).set_index(\"step\").iloc[::sample_step]\n",
    "    val_df = pd.DataFrame(record_dict[\"val\"]).set_index(\"step\")\n",
    "\n",
    "    last_step = train_df.index[-1]  # 最后一步的步数\n",
    "\n",
    "    # print(train_df)\n",
    "    # print(val_df)\n",
    "\n",
    "    # 画图 \n",
    "    fig_num = len(train_df.columns)  # 画两张图,分别是损失和准确率\n",
    "\n",
    "    # plt.subplots：用于创建一个包含多个子图的图形窗口。\n",
    "    # 1：表示子图的行数为 1。\n",
    "    # fig_num：表示子图的列数，即子图的数量。\n",
    "    # figsize=(5 * fig_num, 5)：设置整个图形窗口的大小，宽度为 5 * fig_num，高度为 5。\n",
    "    # fig：返回的图形对象（Figure），用于操作整个图形窗口。\n",
    "    # axs：返回的子图对象（Axes 或 Axes 数组），用于操作每个子图。\n",
    "    fig, axs = plt.subplots(1, fig_num, figsize=(5 * fig_num, 5))\n",
    "    for idx, item in enumerate(train_df.columns):\n",
    "        # train_df.index 是 x 轴数据（通常是 step）。\n",
    "        # train_df[item] 是 y 轴数据（当前指标的值）。\n",
    "        axs[idx].plot(train_df.index, train_df[item], label=\"train:\" + item)\n",
    "        # val_df.index 是 x 轴数据。\n",
    "        # val_df[item] 是 y 轴数据。\n",
    "        axs[idx].plot(val_df.index, val_df[item], label=\"val:\" + item)\n",
    "        axs[idx].grid()  # 显示网格\n",
    "        axs[idx].legend()  # 显示图例\n",
    "        axs[idx].set_xticks(range(0, train_df.index[-1] + 1, 5000))  # 设置x轴刻度\n",
    "        axs[idx].set_xticklabels(map(lambda x: f\"{x // 1000}k\", range(0, last_step + 1, 5000)))  # 设置x轴标签\n",
    "        axs[idx].set_xlabel(\"step\")\n",
    "\n",
    "    plt.show()\n",
    "\n",
    "\n",
    "plot_record_curves(record_dict)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "5d9729d15a0d01e8",
   "metadata": {},
   "source": [
    "## 评估"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 19,
   "id": "3195e10a60c29806",
   "metadata": {
    "ExecuteTime": {
     "end_time": "2025-01-21T14:10:19.121498Z",
     "start_time": "2025-01-21T14:10:19.121498Z"
    },
    "execution": {
     "iopub.execute_input": "2025-01-21T16:07:43.247530Z",
     "iopub.status.busy": "2025-01-21T16:07:43.247266Z",
     "iopub.status.idle": "2025-01-21T16:07:45.913339Z",
     "shell.execute_reply": "2025-01-21T16:07:45.912836Z",
     "shell.execute_reply.started": "2025-01-21T16:07:43.247514Z"
    },
    "tags": []
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Test loss: 0.4336, Test acc: 0.8562\n"
     ]
    }
   ],
   "source": [
    "# 加载最好的模型\n",
    "# torch.load：加载保存的模型权重或整个模型。\n",
    "# \"checkpoints/best.ckpt\"：模型权重文件路径。\n",
    "# weights_only=True：仅加载模型的权重，而不是整个模型（包括结构和参数）。这是 PyTorch 2.1 引入的新特性，用于增强安全性。\n",
    "# map_location=device：将模型加载到当前设备（GPU或CPU）。\n",
    "model.load_state_dict(torch.load(\"checkpoints/08_resnet.ckpt\", weights_only=True, map_location=device))  # 加载最好的模型\n",
    "\n",
    "model.eval()  # 评估模式\n",
    "loss, acc = evaluate(model, eval_loader, loss_fct)\n",
    "print(f\"Test loss: {loss:.4f}, Test acc: {acc:.4f}\")"
   ]
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "Python 3 (ipykernel)",
   "language": "python",
   "name": "python3"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 3
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython3",
   "version": "3.10.14"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 5
}
